/**
 * 
 */
package com.vssr.persist;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.vssr.persist.bean.Associate;
import com.vssr.persist.bean.Column;
import com.vssr.persist.bean.Id;
import com.vssr.persist.bean.Table;
import com.vssr.persist.exception.DomainException;

/**
 * @author Srini
 */
public class DomainObject {

	private boolean isAssociated = false;
	private final Map<String, Field> fieldMap = new HashMap<String, Field>();
	private final Map<String, Associate> associatedObjects = new HashMap<String, Associate>();

	public static List<Object> getDBRecords(Object object, String query,
			Connection conn) throws DomainException {

		if (conn == null) {
			throw new DomainException("Connection object cannot be null");
		}
		if (object == null) {
			throw new DomainException("Custom object/template cannot be null");
		}
		DomainObject obj = new DomainObject();
		List<Object> objectList = obj.getData(object, query, conn);

		return objectList;
	}

	public static List getDBRecords(String query, Connection conn) {
		List mainList = new ArrayList();
		List<Map<String, Object>> list = getUserData(null, query, conn);
		for (Map<String, Object> map : list) {
			Collection l = map.values();
			mainList.add(l);
		}
		return mainList;
	}

	private List<Object> getData(Object objectName, String query,
			Connection conn) {

		List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();

		try {

			String tableName = getTableName(objectName);
			getFieldsMap(objectName);

			dataList = getUserData(tableName, query, conn);

			if (isAssociated) {
				for (Map<String, Object> list : dataList) {
					Iterator<String> itr = associatedObjects.keySet()
							.iterator();
					// for (Associate asso : associatedObjects) {
					while (itr.hasNext()) {
						String entry = itr.next();
						Associate asso = associatedObjects.get(entry);
						Class definedObj = asso.object();
						Object obj = definedObj.newInstance();
						StringBuffer subQuery = new StringBuffer(
								"select * from ");
						subQuery.append(getTableName(obj)).append(" where ");
						subQuery.append(asso.reference()).append(" = ");
						subQuery.append(list.get(asso.column()));

						List subData = getUserData(definedObj.getName(),
								subQuery.toString(), conn);
						subData = fetch(obj, subData);
						String key = entry; // objectName.getClass().getName() +
						// "."+ asso.name().toLowerCase();
						Field f = fieldMap.get(key);
						if (f.getType() != List.class) {
							if (subData != null && !subData.isEmpty()) {
								list.put(asso.name(), subData.get(0));
							}
						} else {
							list.put(asso.name(), subData);
						}
					}
				}
			}
		} catch (DomainException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

		return fetch(objectName, dataList);
	}

	private void getFieldsMap(Object objectName) {
		Field[] fields = objectName.getClass().getDeclaredFields();
		try {
			String key = null, attr = null;
			for (Field field : fields) {
				Annotation[] anns = field.getAnnotations();
				if (anns != null && anns.length > 0) {

					Annotation annn = anns[0];
					if (annn.annotationType() == Column.class
							|| annn.annotationType() == Id.class) {
						attr = field.getAnnotation(Column.class).value();
						key = objectName.getClass().getName() + "."
								+ attr.toLowerCase();
					} else if (annn.annotationType() == Associate.class) {
						Associate ann = field.getAnnotation(Associate.class);
						attr = ann.name();
						isAssociated = true;
						key = objectName.getClass().getName() + "."
								+ attr.toLowerCase();
						associatedObjects.put(key, ann);
						getFieldsMap(ann.object().newInstance());
					}
					// key = objectName.getClass().getName() + "."
					// + attr.toLowerCase();
					fieldMap.put(key, field);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static List<Map<String, Object>> getUserData(String tableName,
			String query, Connection conn) {
		List<Map<String, Object>> mainList = new ArrayList<Map<String, Object>>();
		Map<String, Object> list = null;

		try {
			Statement stmt = conn.createStatement();
			if (query == null || query.isEmpty()) {
				query = "select * from " + tableName;
			}
			ResultSet rs = stmt.executeQuery(query);
			ResultSetMetaData rsmd = rs.getMetaData();
			Object value = null;
			while (rs.next()) {
				list = new HashMap<String, Object>();
				for (int counter = 1; counter <= rsmd.getColumnCount(); counter++) {
					value = rs.getObject(counter);
					value = (value == null) ? "" : value;
					list.put(rsmd.getColumnLabel(counter).toLowerCase(), value);
				}
				mainList.add(list);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return mainList;
	}

	private List<Object> fetch(Object object, List<Map<String, Object>> dataList) {

		List<Object> objList = new ArrayList<Object>();

		try {
			Object[] methodArgs = new Object[1];

			Field f = null;
			Class type = null;
			Method method = null;
			String key = null;
			Object value = null;
			for (Map<String, Object> list : dataList) {
				object = object.getClass().newInstance();
				Iterator itr = list.entrySet().iterator();
				while (itr.hasNext()) {
					Map.Entry entry = (Map.Entry) itr.next();
					key = object.getClass().getName() + "."
							+ entry.getKey().toString().toLowerCase();
					f = fieldMap.get(key);
					if (f != null) {
						type = f.getType();
						String strMethodName = "set" + initCaps(f.getName());
						Class[] paramTypes = { type };
						method = object.getClass().getDeclaredMethod(
								strMethodName, paramTypes);
						value = entry.getValue();
						if (type == String.class) {
							methodArgs[0] = value.toString();
						} else if (type == int.class) { // String to int
							methodArgs[0] = Integer.valueOf(value.toString());
						} else if (type == boolean.class) { // to boolean
							methodArgs[0] = Boolean.valueOf(value.toString());
						} else if (type == long.class) {
							methodArgs[0] = Long.valueOf(value.toString());
						} else {
							methodArgs[0] = value;
						}
						method.invoke(object, methodArgs);
					}
				}
				objList.add(object);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return objList;
	}

	private static String initCaps(String str) {
		// str
		StringBuffer result = new StringBuffer();
		result.append(str.substring(0, 1).toUpperCase()).append(
				str.substring(1, str.length()));

		return result.toString();
	}

	private static String getTableName(Object objectName)
			throws DomainException {

		String tableName = null;
		Table table = objectName.getClass().getAnnotation(Table.class);

		if (table == null) {
			throw new DomainException(
					"No database mapping defined for the object "
							+ objectName.getClass().getName());
		}
		tableName = table.name();
		return tableName;
	}

}
